package utils;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
public class DateUtils {
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");

    public static final DateTimeFormatter SIMPLE_YMD_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    public static final ZoneOffset UNIFIED_TIME_ZONE = ZoneOffset.of("+8");    // unified in China time zone.

    private DateUtils() {

    }

    public static LocalDateTime convertToLocalDateTime(Date date) {
        if (null == date) {
            return null;
        }
        LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        return localDateTime;
    }

    public static String formatLocalDatTime(LocalDateTime localDateTime, DateTimeFormatter formatter) {
        if (null == localDateTime || null == formatter) {
            return null;
        }
        return localDateTime.format(formatter);
    }

    public static String getLastWeekDayByDate(LocalDate date) {
        if (null == date) {
            date = LocalDate.now();
        }
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        if (dayOfWeek.getValue() >= 6) {
            date = date.minusDays(1);
            return getLastWeekDayByDate(date);
        }
        return date.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    }

    public static String getStartDate(String dateStr) {
        if (null == dateStr || dateStr.length() <= 0) {
            return null;
        }
        if (dateStr.contains("-")) {
            return dateStr.split("-")[0];
        }
        return dateStr;
    }

    /**
     * parse the specified date to LocalDateTime
     *
     * @param dateStr format:yyyyMMdd
     * @return
     */
    public static LocalDateTime parseDateFromString(String dateStr) {
        return LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE).atStartOfDay();
    }

    /**
     * get the beginning of the specified day
     *
     * @param localDateTime the parameter will not be changed
     * @return
     */
    public static LocalDateTime getBeginLocalDateTime(final LocalDateTime localDateTime) {
        return localDateTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
    }

    public static LocalDateTime getBeginLocalDateTime(String dateStr) {
        return LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE).atStartOfDay();
    }

    /**
     * get the end of the specified day
     *
     * @param localDateTime the parameter will not be changed
     * @return
     */
    public static LocalDateTime getEndLocalDateTime(final LocalDateTime localDateTime) {
        return localDateTime.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
    }

    public static LocalDateTime getEndLocalDateTime(String dateStr) {
        return LocalDateTime.of(LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE), LocalTime.MAX);
    }

    public static Set<String> parseStrRangeToSet(String dateRangeStr) {
        if (org.apache.commons.lang.StringUtils.isEmpty(dateRangeStr)) {
            return Sets.newHashSet();
        }
        // if the parameter doesn't contain predefined string, then return the origin value directly.
        if (!dateRangeStr.contains("-")) {
            return Sets.newHashSet(dateRangeStr);
        }
        // if the parameter contains multiple separators, parse and format it and then put it into a set
        if (dateRangeStr.indexOf("-") != dateRangeStr.lastIndexOf("-")) {
            Set<String> set = new TreeSet<>();
            String[] dateArray = dateRangeStr.split("-");
            Arrays.stream(dateArray).map(dateStr -> LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE)
                    .format(DateTimeFormatter.BASIC_ISO_DATE));
            return set;
        }
        // if the parameter only contains one separator, calculate the data range and put it into a set
        Set<String> set = new TreeSet<>();
        String[] dateArray = dateRangeStr.split("-");
        String firstParam = dateArray[0];
        String secondParam = dateArray[1];
        set.add(LocalDate.parse(firstParam, DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.BASIC_ISO_DATE));
        set.add(LocalDate.parse(secondParam, DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.BASIC_ISO_DATE));
        Iterator<String> iterator = set.iterator();
        LocalDate tempDate = LocalDate.parse(iterator.next(), DateTimeFormatter.BASIC_ISO_DATE);
        LocalDate endDate = LocalDate.parse(iterator.next(), DateTimeFormatter.BASIC_ISO_DATE);
        while (tempDate.isBefore(endDate)) {
            tempDate = tempDate.plusDays(1);
            set.add(tempDate.format(DateTimeFormatter.BASIC_ISO_DATE));
        }
        return set;
    }

    public static String getSimpleYMDFormatString() {
        try {
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"));
        } catch (Exception e) {
            // log.warn("getSimpleYMDFormatString error.", e);
        }
        return null;
    }

    public static String getSimpleYMDFormatString(String pattern) {
        try {
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(pattern));
        } catch (Exception e) {
            // log.warn("getSimpleYMDFormatString error.", e);
        }
        return null;
    }

    public static LocalDateTime formatStrToLocalDateTime(String dateStr) {
        try {
            return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
        } catch (Exception e) {
            // log.warn("formatStrToLocalDateTime error. dateStr:{}", dateStr, e);
        }
        return null;
    }

    public static LocalDateTime parseTimeStr(String dateStr) {
        try {
            return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
        } catch (Exception e) {
            // log.warn("formatStrToLocalDateTime error. dateStr:{}", dateStr, e);
        }
        return null;
    }

    public static String getCurrentDateTimeStringFormat(ZoneId zoneId) {
        return LocalDateTime.now(zoneId).format(DATE_FORMATTER);
    }

    public static LocalDateTime getLocalDateTime(int minusDays) {
        LocalDateTime now = LocalDateTime.now().minusDays(minusDays);
        return getBeginLocalDateTime(now);
    }

    public static String getSimpleDateFormatString() {
        return LocalDate.now().format(SIMPLE_YMD_FORMATTER);
    }

    public static List<String> parseStrRangeToList(String dateRangeStr) {
        if (StringUtils.isEmpty(dateRangeStr)) {
            return Lists.newArrayListWithCapacity(0);
        }
        // if the parameter doesn't contain predefined string, then return the origin value directly.
        if (!dateRangeStr.contains("-")) {
            return Lists.newArrayList(dateRangeStr);
        }
        // if the parameter contains multiple separators, parse and format it and then put it into a set
        if (dateRangeStr.indexOf("-") != dateRangeStr.lastIndexOf("-")) {
            String[] dateArray = dateRangeStr.split("-");
            return Arrays.stream(dateArray).map(dateStr -> LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE)
                    .format(DateTimeFormatter.BASIC_ISO_DATE)).collect(Collectors.toList());
        }
        // if the parameter only contains one separator, calculate the data range and put it into a set
        Set<String> set = new TreeSet<>();
        String[] dateArray = dateRangeStr.split("-");
        String firstParam = dateArray[0];
        String secondParam = dateArray[1];
        set.add(LocalDate.parse(firstParam, DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.BASIC_ISO_DATE));
        set.add(LocalDate.parse(secondParam, DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.BASIC_ISO_DATE));
        if (set.size() == 1) {
            return new ArrayList<>(set);
        }
        Iterator<String> iterator = set.iterator();
        LocalDate tempDate = LocalDate.parse(iterator.next(), DateTimeFormatter.BASIC_ISO_DATE);
        LocalDate endDate = LocalDate.parse(iterator.next(), DateTimeFormatter.BASIC_ISO_DATE);
        while (tempDate.isBefore(endDate)) {
            tempDate = tempDate.plusDays(1);
            set.add(tempDate.format(DateTimeFormatter.BASIC_ISO_DATE));
        }
        return new ArrayList<>(set);
    }

    public static long parseDateStrToMillisecond(String dateStr) {
        final LocalDateTime dateLocalDateTime = parseDateFromString(dateStr);
        return dateLocalDateTime.toInstant(UNIFIED_TIME_ZONE).toEpochMilli();
    }

    public static String getDateStrFromMillisecond(long millisecond) {
        final LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(millisecond / 1000, 0, ZoneOffset.of("+8"));
        return formatLocalDatTime(localDateTime, SIMPLE_YMD_FORMATTER);
    }


    public static void main(String[] args) {
        long timeMil = parseDateStrToMillisecond("20210815");
        String str = getDateStrFromMillisecond(timeMil);

        long curTimeInChina = LocalDateTime.now().toInstant(UNIFIED_TIME_ZONE).toEpochMilli();
        long curTimeInKorea = LocalDateTime.now().toInstant(ZoneOffset.of("+9")).toEpochMilli();

        System.out.println(str);
    }

}
